pytestのparametrizeでdataclassを利用してみる
pytestのparametrizeを活用すれば、ひとつのテストメソッドに対して、パラメータを変えるテストが便利に書けます。
しかし、parametrizeのパラメータの数が多かったり、数は少なくとも内容が多いとき、テストコードが縦に長くなって見づらくなることがあります。 改善方法を考えているとき、ふと、「そういえば、pytestのparametrizeでdataclassが使えたりするかな?」と閃いたので、試してみました。
おすすめの方
- pytestのparametrizeでdataclassを利用したい方
テストパターンが見づらい例
たとえば、SNSトピックを受け取って処理するLambdaのUnit Testを書く場合を想定します。 パラメータの量や内容が多いと、途中で「これはどれが何だっけ?」「user_info と device_info は、どっちが先だっけ?」みたいになることがあるのです。
import pytest import app import json @pytest.mark.parametrize( "event, user_info, device_info, xxx, expected", [ # パターン1 ( # 先頭なので、これは event だと分かりやすい { "Records": [ { "body": json.dumps( { "Message": json.dumps( { "aaa": "111", "bbb": "222", "aaa": "333", } ) } ) }, { "body": json.dumps( { "Message": json.dumps( { "aaa": "AAA", "bbb": "BBB", "aaa": "CCC", } ) } ) }, { "body": json.dumps( { "Message": json.dumps( { "aaa": "xxx", "bbb": "yyy", "aaa": "zzz", } ) } ) }, ] }, # これはどれが何だっけ? { # 略 }, { # 略 }, { # 略 }, { # 略 }, ), # パターン2 ( { "Records": [ { "body": json.dumps( { "Message": json.dumps( { "aaa": "111", "bbb": "222", "aaa": "333", } ) } ) }, { "body": json.dumps( { "Message": json.dumps( { "aaa": "AAA", "bbb": "BBB", "aaa": "CCC", } ) } ) }, { "body": json.dumps( { "Message": json.dumps( { "aaa": "xxx", "bbb": "yyy", "aaa": "zzz", } ) } ) }, ] }, # user_info と device_info は、どっちが先だっけ? { # 略 }, { # 略 }, { # 略 }, # これは何だっけ? { # 略 }, ), ], ) def test_message(event, user_info, device_info, xxx, expected): # actual = app.main(event, None) # assert actual == expected pass
dataclassを利用してみる(短い版)
Messageという名前でデータクラスを作成して、parametrizeで利用しています。
import pytest import app from dataclasses import dataclass @dataclass class Message(object): text1: str text2: str @pytest.mark.parametrize( "message, expected", [ (Message("hello", "world"), "hello world"), # キーワード引数なし (Message(text1="FOO", text2="BAR"), "FOO BAR"), # キーワード引数あり ], ) def test_message_use_data_class(message, expected): actual = app.message1(message.text1, message.text2) assert actual == expected
dataclassを利用してみる(長い版)
MessageParamsという名前でデータクラスを作成して、parametrizeで利用しています。
データクラスの定義(内容)はいろいろな方針があると思います。
- ひとつのデータクラスで、すべてのパラメータを扱えるようにする
- expectedとそれ以外のデータクラスを作成する(下記の例がこれ)
- それぞれのパラメータごとにデータクラスを作成する
- など
他でも使い回せるか?などを考慮しつつ、自分たちにとって分かりやすい&都合が良い方針を採用すると良いですね。
import pytest import app import json from dataclasses import dataclass @dataclass class MessageParams(object): event: dict user_info: dict device_info: dict xxx: dict @dataclass class Expected(object): expected: dict @pytest.mark.parametrize( "message_params, expected", [ # パターン1 ( MessageParams( event={ "Records": [ { "body": json.dumps( { "Message": json.dumps( { "aaa": "111", "bbb": "222", "aaa": "333", } ) } ) }, { "body": json.dumps( { "Message": json.dumps( { "aaa": "AAA", "bbb": "BBB", "aaa": "CCC", } ) } ) }, { "body": json.dumps( { "Message": json.dumps( { "aaa": "xxx", "bbb": "yyy", "aaa": "zzz", } ) } ) }, ] }, user_info={ # 略 }, device_info={ # 略 }, xxx={ # 略 }, ), Expected( { # 略 } ), ), # パターン2 ( MessageParams( event={ "Records": [ { "body": json.dumps( { "Message": json.dumps( { "aaa": "111", "bbb": "222", "aaa": "333", } ) } ) }, { "body": json.dumps( { "Message": json.dumps( { "aaa": "AAA", "bbb": "BBB", "aaa": "CCC", } ) } ) }, { "body": json.dumps( { "Message": json.dumps( { "aaa": "xxx", "bbb": "yyy", "aaa": "zzz", } ) } ) }, ] }, user_info={ # 略 }, device_info={ # 略 }, xxx={ # 略 }, ), Expected( { # 略 }, ), ), ], ) def test_message(message_params, expected): # actual = app.main(message_params.event, None) # assert actual == expected.expected pass
さいごに
見通しが良いテストコードには過剰かもしれません。使い所には気をつけていきたいですね。